﻿namespace Cellfish.Toolbox.Tasks
{
    using System;
    using System.Linq;
    using System.Threading.Tasks;

    /// <summary>
    /// Provides task combinators.
    /// </summary>
    public static partial class TaskWhen
    {
        /// <summary>
        /// Creates a task that will complete when the supplied task completes. 
        /// The result of the supplied task is negated.
        /// </summary>
        /// <param name="task">The task who's result should be negated.</param>
        /// <returns>A task that represents the completion of the negation of the supplied task.</returns>
        /// <exception cref="System.ArgumentNullException">If task is null.</exception>
        public static Task<bool> Not(Task<bool> task)
        {
            if (task == null)
            {
                throw new ArgumentNullException("task");
            }

            if (task.IsCompleted)
            {
                if (task.IsFaulted || task.IsCanceled)
                {
                    return task;
                }

                return Task.FromResult(!task.Result);
            }

            return DoNot(task);
        }

        /// <summary>
        /// Creates a task that will complete when all of the supplied tasks complete with true result
        /// or when one task completes with false result or fails.
        /// </summary>
        /// <param name="tasks">The tasks to logically And.</param>
        /// <returns>A tasks that represents the logical And of all the supplied tasks.</returns>
        /// <exception cref="System.ArgumentNullException">If task is null.</exception>
        public static Task<bool> And(params Task<bool>[] tasks)
        {
            return DoLogic(tasks, DoAnd);
        }

        /// <summary>
        /// Creates a task that will complete when all of the supplied tasks complete with false result
        /// or when one task completes with true result or fails.
        /// </summary>
        /// <param name="tasks">The tasks to logically Or.</param>
        /// <returns>A tasks that represents the logical Or of all the supplied tasks.</returns>
        /// <exception cref="System.ArgumentNullException">If task is null.</exception>
        public static Task<bool> Or(params Task<bool>[] tasks)
        {
            return DoLogic(tasks, DoOr);
        }

        private static async Task<bool> DoNot(Task<bool> task)
        {
            return !await task;
        }

        private static async Task<bool> DoOr(Task<bool>[] tasks)
        {
            while (tasks.Length > 0)
            {
                var completed = await Task.WhenAny(tasks);
                if (completed.Result)
                {
                    return true;
                }

                tasks = tasks.Where(t => t != completed).ToArray();
            }

            return false;
        }

        private static async Task<bool> DoAnd(Task<bool>[] tasks)
        {
            while (tasks.Length > 0)
            {
                var completed = await Task.WhenAny(tasks);
                if (!completed.Result)
                {
                    return false;
                }

                tasks = tasks.Where(t => t != completed).ToArray();
            }

            return true;
        }

        private static Task<bool> DoLogic(Task<bool>[] tasks, Func<Task<bool>[], Task<bool>> logic)
        {
            if (tasks == null)
            {
                throw new ArgumentNullException("tasks");
            }

            if (tasks.Length == 0)
            {
                return Task.FromResult(false);
            }

            if (tasks.Length == 1)
            {
                return tasks[0];
            }

            return logic(tasks);
        }
    }
}
